﻿namespace SpikingAnalyticsLib.ResponseFingerPrint

open System.IO
open System.Collections.Generic
open SpikingNeuronLib
open SpikingAnalyticsLib
open SpikingAnalyticsLib.PatternUtilities
open SpikingAnalyticsLib.PatternExtensions

//=================================================================================================

/// The neural response to repeated stimulation resulting from PNG activation
/// Neural response data is a set of firing events and their associated empirical firing probabilities
// The firing probability is computed from the probability of window activation for each window, aggregated over multiple frames
// Firing events are generated from the average spike times within each window
type Response (data:(FiringEvent * float) list) =

    static let GetResponse threshold (profile:FrameProfile) (frames:ResponseFingerPrintFrameSet) (windowMapSet:WindowMapSet) =

        let numberOfFrames = frames.Length

        let requiredSpikeListLength =
            let length = threshold * float numberOfFrames
            int length

        // get spike times accumulated over multiple frames
        let spikeTimesByWindow = frames.GetAccumulatedWindowSpikeTimes(windowMapSet.GetWindowStatsForPattern(profile.PatternName))

        // get the firing events corresponding to the window activations (and their probabilities)
        let responseEvents =
            spikeTimesByWindow
            |> Map.toSeq
            |> Seq.filter (fun (_, spikeTimes) -> spikeTimes.Length >= requiredSpikeListLength)
            |> Seq.map (fun ((_, neuronIndex), spikeTimes) ->
                    let event =
                        let time = List.averageBy (fun time -> float time) spikeTimes
                        int (System.Math.Round(time)), neuronIndex
                    let activationProbability =
                        // clip value to a maximum value of 1.0
                        let likelihood = (float spikeTimes.Length) / (float numberOfFrames)
                        if likelihood > 1.0 then 1.0 else likelihood

                    event, activationProbability
                )
            |> Seq.toList

        let patternEvents =
            let pattern = profile.Pattern
            if pattern.IsSome then
                pattern.Value.GetEventTuples()
                |> Seq.map (fun evt -> evt, 1.0)    // assume probability of pattern events is 1.0
                |> Seq.toList
            else
                []

        List.append patternEvents responseEvents
        |> List.sortBy (fun ((time, nindex), _) -> time)
        |> List.map (fun ((time, nindex), probability) -> new FiringEvent(time, nindex, EventLabel.Unspecified), probability)

//---------------------------------------------------------------------------------------------------------
// Constructors

    new (threshold, profile, frames, windowMapSet) =
        let response = GetResponse threshold profile frames windowMapSet
        new Response(response)

//---------------------------------------------------------------------------------------------------------
// Properties

    /// The neural response data as a list of firing events and associated empirical firing probabilities
    member this.Data = data

//---------------------------------------------------------------------------------------------------------
// Methods

    /// Save the response to the specified stream
    member this.Save(sw:StreamWriter) =
        data
        |> Seq.map (fun (evt, probability) -> sprintf "%d %d %f" evt.Time evt.NeuronIndex probability)
        |> Seq.iter (fun line -> sw.WriteLine(line))

    /// Save the response to the specified file
    member this.Save(outputFilePath) =
        use sw = File.CreateText(outputFilePath)
        this.Save(sw)
